package vg.modules.opener.decoders.graphml;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Stack;

import org.apache.commons.lang.StringEscapeUtils;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import vg.modules.opener.decoders.graphml.elements.GraphMLAttribute;
import vg.modules.opener.decoders.graphml.elements.GraphMLGraph;
import vg.modules.opener.decoders.graphml.elements.GraphMLVertex;
import vg.services.data_base_manager.data.header.AttributeHeader;
import vg.services.data_base_manager.data.header.GraphModelHeader;
import vg.services.data_base_manager.data.header.VertexHeader;
import vg.services.data_base_manager.interfaces.IGraphDataBase;
import vg.services.main_manager.MainManager;

class GraphMLParser extends DefaultHandler {
    // Defines
    public static final String GRAPHML_ATTRIBUTE_TYPE_INT = "int";
    public static final String GRAPHML_ATTRIBUTE_TYPE_INTEGER = "integer";
    public static final String GRAPHML_ATTRIBUTE_TYPE_LONG = "long";
    public static final String GRAPHML_ATTRIBUTE_TYPE_FLOAT = "float";
    public static final String GRAPHML_ATTRIBUTE_TYPE_DOUBLE = "double";
    public static final String GRAPHML_ATTRIBUTE_TYPE_REAL = "real";
    public static final String GRAPHML_ATTRIBUTE_TYPE_BOOLEAN = "boolean";
    public static final String GRAPHML_ATTRIBUTE_TYPE_STRING = "string";

    private IGraphDataBase graphDataBase;
    private GraphModelHeader graphModelHeader;
    private int levelGraph = 0;

    // schema parsing
    private String m_key;
    private String m_id;
    private String m_for;
    private String m_name;
    private String m_type;
    private String m_dflt;

    private StringBuffer stringBuffer = new StringBuffer();

    // node, edge, data parsing
    private boolean inSchema;
    private boolean inSubGraphSection = false;
    private HashMap<String, GraphMLAttribute> vertexData = new HashMap<String, GraphMLAttribute>();
    private HashMap<String, GraphMLAttribute> edgeData = new HashMap<String, GraphMLAttribute>();
    private HashMap<String, GraphMLAttribute> graphData = new HashMap<String, GraphMLAttribute>();
    
    private final Stack<GraphMLGraph> lastSubGraph = new Stack<GraphMLGraph>();
    private final Stack<Integer> lastNodes = new Stack<Integer>();
    private final Stack<Integer> lastEdges = new Stack<Integer>();
    private final Stack<List<GraphMLAttribute>> lastAttributes = new Stack<List<GraphMLAttribute>>();

    GraphMLParser(IGraphDataBase graphDataBase, String graphName) {
        this.graphDataBase = graphDataBase;
        this.graphModelHeader = new GraphModelHeader(graphDataBase.createGraphModelHeader(graphName));
        this.graphModelHeader.name = graphName;
    }

    @Override
    public void startDocument() {
        inSchema = true;
    }

    @Override
    public void endDocument() throws SAXException {
        // if we don't reach the end of document, delete graph from model
    }

    @Override
    public void startElement(String namespaceURI, String localName, String qName, Attributes atts) {
        // first clear the character buffer.
        stringBuffer.setLength(0);

        // FIX ME. If we interchange 'if', we have get increase, 
        // because label 'GRAPH' less common than 'NODE' or 'EDGE'.
        // If you read it, make it. And write test which show it=)
        if (qName.equals(GraphMLTokens.GRAPH)) {
            // parse directedness default
            String edef = atts.getValue(GraphMLTokens.EDGEDEF);
            boolean directed = GraphMLTokens.DIRECTED.equalsIgnoreCase(edef);
            String str_id = atts.getValue(GraphMLTokens.ID);
            
            int id = graphDataBase.createGraphHeader(graphModelHeader.id, directed, levelGraph == 0);
            GraphMLGraph tmp_sg = new GraphMLGraph(id, graphModelHeader.id, str_id, directed);

            levelGraph++;

            // add link to inner graph
            if(lastNodes.size() > 0) {
                Integer v_id = lastNodes.peek();
                VertexHeader vh = graphDataBase.getVertexHeader(v_id);
                vh.link2inner_graph = id;
                graphDataBase.modifyVertexHeader(vh);
            }
            
            lastSubGraph.push(tmp_sg);
            lastAttributes.add(new ArrayList<GraphMLAttribute>());
            inSubGraphSection = true;

            for (GraphMLAttribute attr : graphData.values()) {
                if (attr.getStrValue() != null) {
                    lastAttributes.peek().add(new GraphMLAttribute(attr.getName(), attr.getStrValue(), attr.getType()));
                }
            }
        } else if (qName.equals(GraphMLTokens.KEY)) {
            if (!inSchema) {
                throw new RuntimeException("\"" + GraphMLTokens.KEY + "\" elements can not" + " occur after the first node or edge declaration.");
            }
            m_for = atts.getValue(GraphMLTokens.FOR);
            m_id = atts.getValue(GraphMLTokens.ID);
            m_name = atts.getValue(GraphMLTokens.ATTRNAME);
            m_type = atts.getValue(GraphMLTokens.ATTRTYPE);
        } else if (qName.equals(GraphMLTokens.NODE)) {
            inSchema = false;
            inSubGraphSection = false;
            String str_id = atts.getValue(GraphMLTokens.ID);
            int sg_id = lastSubGraph.peek().getId();
            int v_id = graphDataBase.createVertexHeader(sg_id, -1);
            lastSubGraph.peek().addVertex(v_id, str_id);
            lastAttributes.add(new ArrayList<GraphMLAttribute>());
            for (GraphMLAttribute attr : vertexData.values()) {
                if (attr.getStrValue() != null) {
                    lastAttributes.peek().add(new GraphMLAttribute(attr.getName(), attr.getStrValue(), attr.getType()));
                }
            }
            lastNodes.add(v_id);
            
        } else if (qName.equals(GraphMLTokens.EDGE)) {
            inSchema = false;
            inSubGraphSection = false;

            //String str_id = atts.getValue(ID);
            String left = atts.getValue(GraphMLTokens.SOURCE);
            String right = atts.getValue(GraphMLTokens.TARGET);
            
            GraphMLGraph tmp_sg = lastSubGraph.peek();
            
            GraphMLVertex v1 = null, v2 = null;
            for(GraphMLVertex buf : tmp_sg.getVertices()) {
                String id = buf.getStr_id();
                if(id != null && id.equals(left)) {
                    v1 = buf;
                }
                if(id != null && id.equals(right)) {
                    v2 = buf;
                }
            }
            
            int e_id = -1;
            lastAttributes.add(new ArrayList<GraphMLAttribute>());
            if(v1 != null && v2 != null) {
                e_id = graphDataBase.createEdgeHeader(tmp_sg.getId(), v1.getId(), v2.getId());
                for (GraphMLAttribute attr : edgeData.values()) {
                    if (attr.getStrValue() != null) {
                        lastAttributes.peek().add(new GraphMLAttribute(attr.getName(), attr.getStrValue(), attr.getType()));
                    }
                }
            } else {
                MainManager.logger.printInfo("Skip edge between different nested graphs. Left = " + left + " Right = " + right);
            }
            lastEdges.add(e_id);
        } else if (qName.equals(GraphMLTokens.DATA)) {
            m_key = atts.getValue(GraphMLTokens.KEY);
        }
    }

    @Override
    public void endElement(String namespaceURI, String localName, String qName) throws SAXException {
        if (qName.equals(GraphMLTokens.DEFAULT)) {
            // value is in the buffer
            m_dflt = stringBuffer.toString();
        } else if (qName.equals(GraphMLTokens.KEY)) {
            // time to add to the proper schema(s)
            addToSchema();
        } else if (qName.equals(GraphMLTokens.GRAPH)) {
            //NestedGraph e = lastGraph.pop();

            levelGraph--;

            int graphId = lastSubGraph.pop().getId();
            inSubGraphSection = false;
            List<GraphMLAttribute> list_a = lastAttributes.pop();
            if(graphId >= 0) {
                for(GraphMLAttribute buf : list_a) {
                    graphDataBase.createGraphAttributeHeader(graphId, buf.getName(), buf.getStrValue(), buf.getType());
                }
            }
        } else if (qName.equals(GraphMLTokens.NODE)) {
            inSubGraphSection = true;
            int v_id = lastNodes.pop();
            List<GraphMLAttribute> list_a = lastAttributes.pop();
            for (GraphMLAttribute buf : list_a) {
                graphDataBase.createVertexAttributeHeader(v_id, buf.getName(), buf.getStrValue(), buf.getType());
            }
        } else if (qName.equals(GraphMLTokens.EDGE)) {
            inSubGraphSection = true;
            int e_id = lastEdges.pop();
            List<GraphMLAttribute> list_a = lastAttributes.pop();
            if(e_id >= 0) {
                for(GraphMLAttribute buf : list_a) {
                    graphDataBase.createEdgeAttributeHeader(e_id, buf.getName(), buf.getStrValue(), buf.getType());
                }
            }
        } else if (qName.equals(GraphMLTokens.DATA)) {
            GraphMLAttribute graphMLAttribute = null;
            if (inSubGraphSection)
                graphMLAttribute = graphData.get(m_key);
            else if (vertexData.containsKey(m_key))
                graphMLAttribute = vertexData.get(m_key);
            else
                graphMLAttribute = edgeData.get(m_key);

            if (graphMLAttribute == null || graphMLAttribute.getName() == null) {
                throw new SAXException("found data tag in wrong position");
            } else {
                String strValue = StringEscapeUtils.unescapeJava(stringBuffer.toString());

                boolean check = true;
                for (GraphMLAttribute attr : lastAttributes.lastElement()) {
                    if (attr.getName().equals(graphMLAttribute.getName())) {
                        attr.setStrValue(strValue);
                        check = false;
                    }
                }
                if (check)
                    lastAttributes.lastElement().add(new GraphMLAttribute(graphMLAttribute.getName(), strValue, graphMLAttribute.getType()));
            }
        }
    }

    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        stringBuffer.append(ch, start, length);
    }
    
    public Integer getGraphModelId() throws Exception {
        return graphModelHeader.id;
    }

//==============================================================================
//-----------------------PRIVATE METHODS----------------------------------------
    private void addToSchema() throws SAXException {
        if (m_name == null || m_name.length() == 0) {
            throw new SAXException("Empty " + GraphMLTokens.KEY + " name.");
        }
        if (m_type == null || m_type.length() == 0) {
            throw new SAXException("Empty " + GraphMLTokens.KEY + " type.");
        }

        int type = graphMLAttributeType2VisualGraphAttributeType(m_type);
        String strValue = m_dflt;

        if (m_for == null || m_for.equals(GraphMLTokens.ALL)) {
            vertexData.put(m_id, new GraphMLAttribute(m_name, strValue, type));
            edgeData.put(m_id, new GraphMLAttribute(m_name, strValue, type));
            graphData.put(m_id, new GraphMLAttribute(m_name, strValue, type));
        } else if (m_for.equals(GraphMLTokens.NODE)) {
            vertexData.put(m_id, new GraphMLAttribute(m_name, strValue, type));
        } else if (m_for.equals(GraphMLTokens.EDGE)) {
            edgeData.put(m_id, new GraphMLAttribute(m_name, strValue, type));
        } else if (m_for.equals(GraphMLTokens.GRAPH)) {
            graphData.put(m_id, new GraphMLAttribute(m_name, strValue, type));
        } else {
            throw new SAXException("Unrecognized \"" + GraphMLTokens.FOR + "\" value: " + m_for);
        }

        m_dflt = null;
    }

    private static int graphMLAttributeType2VisualGraphAttributeType(String type) {
        if (type == null)
            return AttributeHeader.NO_ATTRIBUTE_TYPE;

        if (type.equals(GRAPHML_ATTRIBUTE_TYPE_STRING))
            return AttributeHeader.STRING_ATTRIBUTE_TYPE;

        if (type.equals(GRAPHML_ATTRIBUTE_TYPE_BOOLEAN))
            return AttributeHeader.BOOLEAN_ATTRIBUTE_TYPE;

        if (type.equals(GRAPHML_ATTRIBUTE_TYPE_DOUBLE) || type.equals(GRAPHML_ATTRIBUTE_TYPE_FLOAT) || type.equals(GRAPHML_ATTRIBUTE_TYPE_REAL))
            return AttributeHeader.DOUBLE_ATTRIBUTE_TYPE;

        if (type.equals(GRAPHML_ATTRIBUTE_TYPE_INT) || type.equals(GRAPHML_ATTRIBUTE_TYPE_INTEGER) || type.equals(GRAPHML_ATTRIBUTE_TYPE_LONG))
            return AttributeHeader.INTEGER_ATTRIBUTE_TYPE;

        return AttributeHeader.STRING_ATTRIBUTE_TYPE;
    }
}